using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using Pinwheel.Toolkits;

namespace Pinwheel.LSerialize
{
    public unsafe partial struct LStreamReader : IDisposable
    {
        [ThreadStatic] private static LStreamReader* s_Default;
        public static LStreamReader* Default
        {
            get
            {
                if (null! == s_Default)
                {
                    s_Default = Native.Current.AllocAndClear<LStreamReader>();
                    s_Default->buffer = LStreamBuffer.Create();
                    s_Default->assist = LStreamBuffer.Create();
                }
                return s_Default;
            } 
        }

        public LStreamReader* Ptr
        {
            get
            {
                fixed (LStreamReader* ptr = &this) return ptr;
            }
        }

        private LStreamBuffer* buffer;
        public LStreamBuffer* Buffer
        {
            get => buffer;
            set => buffer = value;
        }
        
        private LStreamBuffer* assist;

        static LStreamReader()
        {
            if (null! == s_Default)
            {
                s_Default = Native.Current.AllocAndClear<LStreamReader>();
                s_Default->buffer = LStreamBuffer.Create();
                s_Default->assist = LStreamBuffer.Create();
            }
        }

        // public LStreamReader(int size = LStreamBuffer.DefaultSize)
        // {
        //     buffer = LStreamBuffer.Create(size);
        //     assist = LStreamBuffer.Create(size);
        // }

        public LStreamReader(byte* source, int size)
        {
            buffer = LStreamBuffer.Create(source, size);
            assist = LStreamBuffer.Create();
        }

        public LStreamReader(LStreamBuffer* source)
        {
            buffer = source;
            assist = LStreamBuffer.Create();
        }

        public void Dispose()
        {
            // buffer->Dispose();
            Native.Current.Free(buffer);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int Seek(int offset, SeekOrigin loc) => Buffer->Seek(offset, loc);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte[] GetBuffer() => Buffer->GetBuffer();

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ILFormatter<T> GetFormatter<T>() => LFormatterProvider.GetFormatter<T>();

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ILFormatter GetFormatter(Type type) => LFormatterProvider.GetFormatter(type);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte ReadByte()
        {
            return Buffer->PopByte();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public UInt16 ReadUInt16()
        {
            // return (UInt16)((UInt16)Buffer->PopByte() | 
            //                 ((UInt16)Buffer->PopByte()) << 8);
            UInt16 result = default;
            ReadUnmanagedValue(ref result);
            return result;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public UInt32 ReadUInt32()
        {
            // return (UInt32) ((UInt32)Buffer->PopByte() | 
            //                  ((UInt32)Buffer->PopByte()) << 8 |
            //                  ((UInt32)Buffer->PopByte()) << 16 |
            //                  ((UInt32)Buffer->PopByte()) << 24);
            UInt32 result = default;
            ReadUnmanagedValue(ref result);
            return result;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public UInt64 ReadUInt64()
        {
            // return (UInt64) ((UInt64)Buffer->PopByte() | 
            //                  ((UInt64)Buffer->PopByte()) << 8 |
            //                  ((UInt64)Buffer->PopByte()) << 16 |
            //                  ((UInt64)Buffer->PopByte()) << 24 |
            //                  ((UInt64)Buffer->PopByte()) << 32 |
            //                  ((UInt64)Buffer->PopByte()) << 40 |
            //                  ((UInt64)Buffer->PopByte()) << 48 |
            //                  ((UInt64)Buffer->PopByte()) << 56);

            UInt64 result = default;
            ReadUnmanagedValue(ref result);
            return result;
        }

        // [MethodImpl(MethodImplOptions.AggressiveInlining)]
        // public UInt128 ReadUInt128()
        // {
        //     return ReadUInt64() | (UInt128)ReadUInt64() << 64;
        // }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void UnsafeReadUnmanagedValue<T>(ref T value)
        {
            int length = Unsafe.SizeOf<T>();
            fixed (void* ptr = &value)
            {
                if (Buffer->EnsureTail(length))
                {
                    Native.Utils.Copy(ptr, Buffer->Current, length);
                    Buffer->Seek(length, SeekOrigin.Current);
                }
                else
                {
                    value = default;
                }
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void ReadUnmanagedValue<T>(ref T value) where T : unmanaged
        {
            int length = sizeof(T);
            fixed (void* ptr = &value)
            {
                if (Buffer->EnsureTail(length))
                {
                    Native.Utils.Copy(ptr, Buffer->Current, length);
                    Buffer->Seek(length, SeekOrigin.Current);
                }
                else
                {
                    value = default;
                }
            }
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void ReadUnmanagedValue<T>(ref T? value) where T : unmanaged
        {
            int length = sizeof(T?);
            fixed (void* ptr = &value)
            {
                if (Buffer->EnsureTail(length))
                {
                    Native.Utils.Copy(ptr, Buffer->Current, length);
                    Buffer->Seek(length, SeekOrigin.Current);
                }
                else
                {
                    value = default;
                }
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe T[] ReadUnmanagedArray<T>() where T : unmanaged
        {
            if (Buffer->EnsureTail(sizeof(uint)))
            {
                uint length = ReadUInt32();
                if (0 == length) return Array.Empty<T>();
                var arr = new T[length];
                var size = (int)length * sizeof(T);
                if (Buffer->EnsureTail(size))
                {
                    fixed (T* ptr = arr) Native.Utils.Copy(ptr, Buffer->Current, size);
                    // void* ptr = Unsafe.AsPointer(ref arr);
                    // Native.Utils.Copy(ptr, Buffer->Current, size);
                    Buffer->Seek(size, SeekOrigin.Current);
                    return arr;  
                }
                Buffer->Seek(-sizeof(uint), SeekOrigin.Current);
            }
            return null;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe T[] UnsafeReadUnmanagedArray<T>()
        {
            if (Buffer->EnsureTail(sizeof(uint)))
            {
                uint length = ReadUInt32();
                if (0 == length) return Array.Empty<T>();
                var arr = new T[length];
                var size = (int)length * Unsafe.SizeOf<T>();
                if (Buffer->EnsureTail(size))
                {
                    fixed (void* ptr = arr) Native.Utils.Copy(ptr, Buffer->Current, size);
                    // void* ptr = Unsafe.AsPointer(ref arr);
                    // Native.Utils.Copy(ptr, Buffer->Current, size);
                    Buffer->Seek(size, SeekOrigin.Current);
                    return arr; 
                }
                Buffer->Seek(-sizeof(uint), SeekOrigin.Current);
            }
            return null;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte[] ReadBytes(int length = 0)
        {
            if (0 == length)
            {
                Buffer->EnsureTail(sizeof(uint));
                length = (int)ReadUInt32();
            }

            return Buffer->PopBytes(length);
        }

        // [MethodImpl(MethodImplOptions.AggressiveInlining)]
        // public T[] ReadBytes<T>(int length = 0) where T : unmanaged
        // {
        //     if (0 == length)
        //     {
        //         Buffer->EnsureTail(sizeof(uint));
        //         length = (int)ReadUInt32();
        //     }
        //     return Buffer->PopBytes<T>(length);
        // }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public string ReadString()
        {
            string result = null;
            if (Buffer->EnsureTail(sizeof(uint)))
            {
                var length = (int)ReadUInt32();
                if (Buffer->EnsureTail(length))
                {
                    result = Encoding.UTF8.GetString(Buffer->Current, length);
                    Buffer->Seek(length, SeekOrigin.Current);
                }
                else
                {
                    Buffer->Seek(-sizeof(uint), SeekOrigin.Current);
                }
            }
            return result;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void ReadUniversal<T>(LSerializeContext* context, ref T value)
        {
            var type = typeof(T);
            if (type.IsBuiltin())
            {
                GetFormatter<T>().Deserialize(context, Ptr, ref value);
            }
            else
            {
                ReadManagedValue<T>(context, ref value);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Reset(int size) => Buffer->Reset(size);

        [MethodImpl(MethodImplOptions.AggressiveInlining)] 
        public void ReadNumber<T>(LSerializeContext* context, ref T value) where T : unmanaged, IEquatable<T>
        {
            var size = sizeof(ValHeader) + sizeof(ulong);
            if (assist->EnsureTail(size))
            {
                // Log.Info($"Read number ref start: {assist->Remain} {typeof(T).Name}");
                assist->HandleUnmanagedStruct(out ValHeader* header);
                assist->HandleUnmanagedStruct(out ulong* instIdPtr);
                // Assert.Check(context->Tag <= header->Tag, "ReadBufferSpan tag not match!");
            
                if (context->Depth == header->Depth && context->Tag == header->Tag)
                {
                    value = *((T*)instIdPtr);
                }
                else
                {
                    value = default;
                    assist->Seek(-size, SeekOrigin.Current);
                } 
                // Log.Info($"Read number ref done: {value}");
            }
            
            context->IncreaseTag();
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)] 
        public void ReadNumber<T>(LSerializeContext* context, ref T? value) where T : unmanaged
        {
            var size = sizeof(ValHeader) + sizeof(ulong);
            if (assist->EnsureTail(size))
            {
                // Log.Info($"Read number ref start: {assist->Remain} {typeof(T).Name}");
                assist->HandleUnmanagedStruct(out ValHeader* header);
                assist->HandleUnmanagedStruct(out ulong* instIdPtr);
                // Assert.Check(context->Tag <= header->Tag, "ReadBufferSpan tag not match!");
            
                if (context->Depth == header->Depth && context->Tag == header->Tag)
                {
                    value = *((T*)instIdPtr);
                }
                else
                {
                    value = default;
                    assist->Seek(-size, SeekOrigin.Current);
                } 
                // Log.Info($"Read number ref done: {value}");
            }
            
            context->IncreaseTag();
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)] 
        public T ReadNumber<T>(LSerializeContext* context) where T : unmanaged
        {
            var size = sizeof(ValHeader) + sizeof(ulong);
            T value = default;
            if (assist->EnsureTail(size))
            {
                // Log.Info($"Read number start: {assist->Remain} {typeof(T).Name}");
                assist->HandleUnmanagedStruct(out ValHeader* header);
                assist->HandleUnmanagedStruct(out ulong* instIdPtr);
                // Assert.Check(context->Tag <= header->Tag, "ReadBufferSpan tag not match!");
            
                if (context->Depth == header->Depth && context->Tag == header->Tag)
                {
                    value = *((T*)instIdPtr);
                }
                else
                {
                    assist->Seek(-size, SeekOrigin.Current);
                } 
                // Log.Info($"Read number done: {value}");
            }
            
            context->IncreaseTag();
            return value;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void ReadString(LSerializeContext* context, ref string value)
        {
            if (ReadBufferSpan(context, ref value, out var span))
            {
                // Log.Info($"Read string start: {span.Buffer->Remain} string");
                var origin = buffer;
                buffer = span.Buffer;
                buffer->Seek(0, SeekOrigin.Begin);
                buffer->HandleUnmanagedStruct(out RefHeader* header);
                value = Encoding.UTF8.GetString(buffer->Current, header->Size);
                buffer = origin;
                LSerializeExtension.CacheObj.CacheSerializeObj(header->InstId, ref value);
                // Log.Info($"Read string done: {value}");
            } 
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Disassemble()
        {
            var offset = sizeof(int);
            
            // Log.Info($"Disassemble buffer before, buffer: {BitConverter.ToString(buffer->GetBuffer())}");
            
            buffer->HandleUnmanagedStruct(out int* refHeaderSize);

            var refSize = *refHeaderSize;
            buffer->Seek(refSize, SeekOrigin.Current);

            buffer->HandleUnmanagedStruct(out int* valHeaderSize);
            
            var valSize = *valHeaderSize;
            if (valSize > 0)
            {
                assist->EnsureSize(valSize);
                assist->Seek(0, SeekOrigin.Begin);
                Native.Utils.Copy(assist->Current, buffer->Current, valSize);
                // assist->Seek(valSize, SeekOrigin.Current);
                // assist->SignSize(false);
                assist->Reset(valSize);
            }

            buffer->Reset(refSize + offset);
            buffer->SignSize(false);
            
            // Log.Info($"Disassemble buffer after, buffer: {BitConverter.ToString(buffer->GetBuffer())}");
            // Log.Info($"Disassemble buffer after, assist: {BitConverter.ToString(assist->GetBuffer())}");

            LSerializeExtension.Cache<LStreamBufferSpan>.RemoveAllCache();
            if (valSize > 0) BuildBufferSpanCache();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void BuildBufferSpanCache()
        {
            var offset = sizeof(RefHeader);
            // Log.Info($"Build buffer span cache: {BitConverter.ToString(buffer->GetBuffer())}");
            while (buffer->Index < buffer->Tail)
            {
                // Log.Info(BitConverter.ToString(buffer->GetBuffer(), buffer->Index, offset));
                HandleUnmanagedStruct(out RefHeader* header);
                var size = header->Size;
                var span = buffer->AsSpan(size + offset, -offset);
                // Log.Info($"Build span cache: {header->InstId} {span.Buffer->Remain}, buffer: {buffer->Size}  {size} {offset} {size + offset}, bytes: {BitConverter.ToString(buffer->GetBuffer(), buffer->Index, size)}");
                LSerializeExtension.Cache<LStreamBufferSpan>.CreateCache(header->InstId, ref span);
                buffer->Seek(size, SeekOrigin.Current);
            }
        }
    }
}